package zdream.pmfield.stage.object;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import zdream.pmfield.data.foe.Ability;
import zdream.pmfield.data.foe.BStat;
import zdream.pmfield.data.foe.Foe;
import zdream.pmfield.data.foe.MoveCheckPair;
import zdream.pmfield.data.foe.MoveTemplate;
import zdream.pmfield.data.foe.MoveType;
import zdream.pmfield.data.foe.Type;
import zdream.pmfield.data.foe.VStat;
import zdream.pmfield.stage.Stage;
import zdream.pmfield.stage.Timeline;
import zdream.pmfield.stage.buff.foe.AbilityExecutingBuff;
import zdream.pmfield.stage.buff.foe.FoeOriginalBuff;
import zdream.pmfield.stage.director.AttackContext;
import zdream.pmfield.stage.buff.foe.FoeStageBuff;
import zdream.pmfield.stage.buff.foe.MoveExecutingBuff;
import zdream.pmfield.stage.buff.StageBuff;
import zdream.pmfield.stage.buff.foe.TypeBuff;
import zdream.pmfield.stage.director.Roll;
import zdream.pmfield.stage.director.HookStore;
import zdream.pmfield.stage.director.HookType;
import zdream.pmfield.stage.director.Hooks;
import zdream.pmfield.stage.director.IHookStorage;
import zdream.pmfield.stage.message.FoeBStatLevelChangeMessage;

/**
 * @author Zdream
 * @date 2023-11-10
 * @since 0.0.1
 */
public class FoeActor extends ObjectOnSeat implements IHookStorage {
	public static final String ORIGINAL_BUFF_CATEGORY = "foeOriginal";

	public final Foe foe;
	public int side;
	public final List<FoeStageBuff> buffs = new ArrayList<>();
	protected final transient HookStore hookStore;

	/**
	 * 如果两个 {@link Foe} 速度是一样的, 那它们俩的次序就以 innerId 进行排序了.
	 */
	public final int innerId;

	/**
	 * 一般为 null. 只有体型很大的 {@link FoeActor} 才有这个
	 */
	public Shadow[] shadows;

	/**
	 * {@link Stage} 计算使用的数值.
	 * 需要将中毒等效果和能力等级都计算在内, 也就是实时当前的数值.
	 */
	public final short[]
			bStats = new short[BStat.values().length],
			vStats = new short[VStat.values().length];

	public FoeActor(Foe foe, Stage stage) {
		this(foe, stage, null);
	}

	public FoeActor(Foe foe, Stage stage, FoeActorSpec spec) {
		super(stage);
		this.foe = Objects.requireNonNull(foe);
		this.innerId = stage.getNextIdAndIncrease();

		if (spec == null || spec.hookStoreSupplier == null) {
			hookStore = new HookStore();
		} else {
			hookStore = Objects.requireNonNull(spec.hookStoreSupplier.get());
		}

		initStatAndTypes();
		initMoves((spec == null) ? null : spec.stageMovesSupplier);
		this.currentShield = foe.maxShield;
	}

	public static class FoeActorSpec {
		public Supplier<HookStore> hookStoreSupplier;
		public Supplier<List<MoveTemplate>> stageMovesSupplier;
	}

	public void init() {
		for (Ability ability : foe.abilities) {
			AbilityExecutingBuff buff = new AbilityExecutingBuff(this, ability);
			this.addBuff(buff);
			this.removeBuff(buff);
		}
	}

	public boolean isFoe() {
		return true;
	}

	public FoeActor asFoe() {
		return this;
	}

	/* **********
	 * Timeline *
	 ********** */

	/**
	 * <p>用于计算速度, 交给 {@link Timeline} 计算.
	 * <p>speedCostRate 是一个百分比值, 一般是 1, 可以这么理解:
	 * 比如有一个 foe 正常情况下一轮等待耗时是 0.8 round,
	 * <pre>
	 * speedCost = 0.8f
	 * </pre>
	 * 那么如果它使用了蓄力技能, 导致这轮需要用平常时间的 1.5 倍, 那么:
	 * <pre>
	 * speedCostExtraRate = 1.5f
	 * </pre>
	 * 这轮结束之后, foe 进入等待状态, 于是:
	 * <pre>
	 * speedRemains = 0.8f x 1.5f = 1.2f
	 * speedCostExtraRate = 1.0f // 重置
	 * </pre>
	 * 也就是说, 下一轮要等 1.2 round 后才开始.
	 * </p>
	 */
	public float speedCost, speedRemains, speedCostRate = 1f;

	/* ************
	 * Basic Stat *
	 ************ */

	private void initStatAndTypes() {
		System.arraycopy(foe.vStats, 0, this.vStats, 0, this.vStats.length);
		System.arraycopy(foe.bStats, 0, this.bStats, 0, this.bStats.length);

		addBuff(new FoeOriginalBuff(this));
		for (Type type : foe.template.types) {
			TypeBuff buff = new TypeBuff(this, type);
			addBuff(buff);
		}
	}

	public boolean isFainting() {
		return foe.currentHp == 0;
	}

	public List<Type> findTypes() {
		return buffs.stream()
				.filter(buff -> buff.is(TypeBuff.CATEGORY))
				.map(buff -> ((TypeBuff) buff).type)
				.collect(Collectors.toList());
	}

	public void refreshVStat() {
		System.arraycopy(foe.vStats, 0, this.vStats, 0, this.vStats.length);

		// 改变数值都放在 priority 小于零的, 数值变化后的 hooks 都放在 priority 大于零的
		final Hooks.FoeBuffContext context = triggerHook(HookType.ON_FOE_REFRESH_VSTAT).as();
		context.withFoe(this).run();
	}

	public void refreshBStat() {
		System.arraycopy(foe.bStats, 0, this.bStats, 0, this.bStats.length);

		// 改变数值都放在 priority 小于零的, 数值变化后的 hooks 都放在 priority 大于零的
		final Hooks.RefreshFoeBStatContext context = triggerHook(HookType.ON_FOE_REFRESH_BSTAT).as();
		context.withFoe(this).run();
	}

	public int getBStat(BStat bStat) {
		return this.bStats[bStat.ordinal()];
	}

	public int getVStat(VStat vStat) {
		return this.vStats[vStat.ordinal()];
	}

	public int getBStatLevel(BStat bStat) {
		return findOriginalBuff().getBStatLevel(bStat);
	}

	public BStatLevelUpdateReport updateBStatLevel() {
		return new BStatLevelUpdateReport(this);
	}

	public static class BStatLevelUpdateItem {
		public final BStat bStat;
		public final int level;
		public final boolean change;

		public StageBuff<?> rejectBy;
		public boolean accept = true;

		/**
		 * 以下两项由 {@link FoeOriginalBuff} 填写
		 */
		public byte levelFrom, levelTo;

		public BStatLevelUpdateItem(BStat bStat, int level, boolean change) {
			this.bStat = Objects.requireNonNull(bStat);
			this.level = level;
			this.change = change;
		}

		public void reject(StageBuff<?> rejectBy) {
			this.rejectBy = Objects.requireNonNull(rejectBy);
			this.accept = false;
		}

		public void setLevelChange(byte from, byte to) {
			this.levelFrom = from;
			this.levelTo = to;
		}
	}

	public static class BStatLevelUpdateReport {
		public final FoeActor foe;
		public final Map<BStat, BStatLevelUpdateItem> items = new HashMap<>();
		public String title;

		public BStatLevelUpdateReport(FoeActor foe) {
			this.foe = Objects.requireNonNull(foe);
		}

		public BStatLevelUpdateReport withBStatChange(BStat bStat, int level) {
			items.put(bStat, new BStatLevelUpdateItem(bStat, level, true));
			return this;
		}

		public BStatLevelUpdateReport withBStatSet(BStat bStat, int level) {
			items.put(bStat, new BStatLevelUpdateItem(bStat, level, false));
			return this;
		}

		public BStatLevelUpdateReport withTitle(String title) {
			this.title = title;
			return this;
		}

		public void commit() {
			final Hooks.FoeBStatLevelUpdateContext context = foe.triggerHook(HookType.ON_FOE_UPDATE_BSTAT_LEVEL).as();
			context.append(foe.seat);
			context.withReport(this).run();

			final FoeOriginalBuff buff = foe.findOriginalBuff();
			buff.updateBStatLevel(this);
			foe.stage.pushMessage(new FoeBStatLevelChangeMessage(this));

			foe.refreshBStat();
		}
	}

	/* ***********
	 *   Moves   *
	 *********** */

	private void initMoves(Supplier<List<MoveTemplate>> stageMovesSupplier) {
		// moves
		for (MoveTemplate move : foe.moves) {
			final MoveType moveType = move.attackerUsing;
			if (moveType == MoveType.MOVEMENT) {
				this.stageMoves.add(new StageMove(this, move));
				continue;
			}

			if (this.attackMoves.size() >= 4) {
				continue;
			}

			this.attackMoves.add(new StageMove(this, move));
		}

		// stageMoves
		if (stageMovesSupplier == null) {
			return;
		}

		final List<MoveTemplate> moveTemplates = Objects.requireNonNull(stageMovesSupplier.get());
		for (MoveTemplate moveTemplate : moveTemplates) {
			this.stageMoves.add(new StageMove(this, moveTemplate));
		}
	}

	/**
	 * 攻击类技能, 一般只能带 4 个, 包括:
	 * <li>{@link zdream.pmfield.data.foe.MoveType#PHYSICAL}
	 * <li>{@link zdream.pmfield.data.foe.MoveType#SPECIAL}
	 * <li>{@link zdream.pmfield.data.foe.MoveType#STATUS}
	 * </li>
	 */
	public final List<StageMove> attackMoves = new ArrayList<>();

	/**
	 * 行为技能, 数量可以超过 4 个, 比如跳过回合、使用物品等, 包括:
	 * <li>{@link zdream.pmfield.data.foe.MoveType#MOVEMENT}
	 * </li>
	 * 一般是战斗时临时加进去的
	 */
	public final List<StageMove> stageMoves = new ArrayList<>();

	public StageMove findMove(MoveTemplate move) {
		for (StageMove foeMove : attackMoves) {
			if (foeMove.move == move) {
				return foeMove;
			}
		}
		for (StageMove foeMove : stageMoves) {
			if (foeMove.move == move) {
				return foeMove;
			}
		}
		return null;
	}

	public boolean hasMove(MoveTemplate move) {
		return findMove(move) != null;
	}

	public StageMove findSkipTurnMove() {
		for (StageMove foeMove : stageMoves) {
			if (foeMove.move.id == Stage.SKIP_TURN_MOVE_ID) {
				return foeMove;
			}
		}
		throw new RuntimeException("无法寻找到技能: 移动");
	}

	public StageMove findMoveToMove() {
		for (StageMove foeMove : stageMoves) {
			if (foeMove.move.id == Stage.MOVE_TO_MOVE_ID) {
				return foeMove;
			}
		}
		throw new RuntimeException("无法寻找到技能: 移动");
	}

	/* **************
	 * Action Point *
	 ************** */

	public static final String TITLE_MOVE_POINT = "movePoint";
	public static final byte MOVE_POINT_MAX = 3;

	/**
	 * <p>行动点 actionPoint 和移动点 movePoint
	 * (For The King 2 中, 行动点叫 "主要行动点", 移动点叫 "次要行动点")
	 * <p>现在技能 {@link MoveTemplate} 区分耗费的是行动点还是移动点.
	 * 技能没有特殊说明全部是耗费行动点, 行动点耗尽时, 无轮是否有移动点都结束该轮次 (同 For The King 2);
	 * 移动点用来特殊的技能, 比如移动技能、吃补给等. 移动点耗尽不会结束该轮次.
	 * <p>Foe 每次行动时默认增加 1 行动点和至少 1 个移动点 (除非有其它取消移动点的状态).
	 * 移动点的数量是 1 个移动点保底加 1 次 {@link VStat#FLEXIBILITY} 判定, 判定成功再加 1 点.
	 * 不使用的移动点能保留到下回合, 但是轮次开始前不会超过 3 点.
	 * </p>
	 */
	public byte actionPoint, movePoint;

	public boolean hasActionPoint() {
		return actionPoint != 0;
	}

	public void restoreActionPoint(int actionPoint, int movePoint) {
		this.actionPoint = (byte) actionPoint;
		this.movePoint = (byte) Math.min(MOVE_POINT_MAX, movePoint + this.movePoint);
	}

	/**
	 * check if the foe has enough action points and move points.
	 *
	 * 检查是否有足够的行动点和移动点.
	 * 注意, 如果 action point == 0, 无论如何都判定为不够.
	 */
	public boolean hasEnoughActionPointsForMove(StageMove move) {
		if (move.costAllActionPoint) {
			if (!hasActionPoint()) {
				return false;
			}
		} else if (move.actionPoint > actionPoint) {
			return false;
		}

		if (move.costAllMovePoint) {
			return true;
		}
		return move.movePoint <= movePoint;
	}

	/**
	 * 扣除 action points 和 move points
	 */
	public void deductActionPoints(StageMove move) {
		if (!hasEnoughActionPointsForMove(move)) {
			throw new IllegalStateException(
					"使用了技能 " + move + " 之后行动力为负了: 使用前行动力=" + actionPoint + ", 移动力=" + movePoint);
		}

		this.actionPoint -= getActionPointsCost(move);
		this.movePoint -= getMovePointsCost(move);
	}

	public byte getActionPointsCost(StageMove move) {
		return (move.costAllActionPoint) ? this.actionPoint : move.actionPoint;
	}

	public byte getMovePointsCost(StageMove move) {
		return (move.costAllMovePoint) ? this.movePoint : move.movePoint;
	}

	/* ***********
	 *    HP     *
	 *********** */
	public int currentShield;

	public int getCurrentHp() {
		return foe.currentHp;
	}

	public int getCurrentShield() {
		return currentShield;
	}

	public void receiveDamage(int damage) {
		int remain = damage;
		if (this.currentShield >= remain) {
			this.currentShield -= remain;
			return;
		}

		remain -= this.currentShield;
		this.currentShield = 0;

		this.foe.currentHp -= remain;
		if (foe.currentHp < 0) {
			foe.currentHp = 0;
		}
	}

	public void receiveDamageIgnoreShield(int damage) {
		this.foe.currentHp -= damage;
		if (foe.currentHp < 0) {
			foe.currentHp = 0;
		}
	}

	/* ************
	 *   Buffs    *
	 ************ */

	public ReceiveBuffReport receiveBuff(FoeStageBuff buff) {
		if (buff.target != this) {
			throw new IllegalArgumentException("buff.foe is " + buff.target + ", not me! I am " + this);
		}

		final Hooks.BeforeFoeReceiveBuffContext context = triggerHook(HookType.BEFORE_FOE_RECEIVE_BUFF).as();
		context.withOtherBuff(buff).run();
		if (context.aborted) {
			return ReceiveBuffReport.ofReject(buff, context.abortBy.owner);
		}

		addBuff(buff);
		return ReceiveBuffReport.ofAccept(buff);
	}

	public void receiveBuffFromAbility(FoeStageBuff buff) {
		if (buff.target != this) {
			throw new IllegalArgumentException("buff.foe is " + buff.target + ", not me! I am " + this);
		}

		// 从 ability 里面创建的 buff 都不需要通过 BEFORE_FOE_RECEIVE_BUFF 检查
		addBuff(buff);
	}

	public void addMoveExecutingBuff(StageMove move) {
		if (move.foe != this) {
			throw new IllegalArgumentException(
					"move.foe 必须是我! move=" + move + ", move.foe=" + move.foe + ", I am " + this);
		}
		this.addBuff(move.createExecutingBuff());
	}

	protected void addBuff(FoeStageBuff buff) {
		this.buffs.add(buff);
		buff.init();
	}

	public void putCheckPairToMoveExecutingBuff(MoveCheckPair pair) {
		final List<FoeStageBuff> moveBuffList = this.buffs.stream()
				.filter(buff -> buff.is(MoveExecutingBuff.CATEGORY))
				.collect(Collectors.toList());
		for (FoeStageBuff buff : moveBuffList) { // 虽然这么写, 但是 buff 应该只能有一个
			((MoveExecutingBuff) buff).putHooksWithMoveEffect(pair);
		}
	}

	public void removeMoveExecutingBuff() {
		final List<FoeStageBuff> moveBuffList = this.buffs.stream()
				.filter(buff -> buff.is(MoveExecutingBuff.CATEGORY))
				.collect(Collectors.toList());
		for (FoeStageBuff buff : moveBuffList) {
			removeBuff(buff);
		}
	}

	public MoveExecutingBuff findMoveExecutingBuff() {
		return this.buffs.stream()
				.filter(buff -> buff.is(MoveExecutingBuff.CATEGORY))
				.map(buff -> (MoveExecutingBuff) buff)
				.findAny()
				.orElse(null);
	}

	public List<FoeStageBuff> findBuffs(String category) {
		return this.buffs.stream().filter(buff -> buff.is(category)).collect(Collectors.toList());
	}

	public List<FoeStageBuff> findBuffs(String... categories) {
		return this.buffs.stream()
				.filter(buff -> Arrays.stream(categories).allMatch(buff::is))
				.collect(Collectors.toList());
	}

	public FoeOriginalBuff findOriginalBuff() {
		final List<FoeStageBuff> list = findBuffs(ORIGINAL_BUFF_CATEGORY);
		if (list.size() != 1) {
			throw new IllegalStateException("FoeActor: " + this + " 的 FoeOriginalBuff 数量不为 1");
		}
		return (FoeOriginalBuff) list.get(0);
	}

	public void removeBuff(FoeStageBuff buff) {
		this.buffs.remove(buff);
		buff.destroy();
	}

	public boolean hasBuff(String category) {
		return this.buffs.stream().anyMatch(buff -> buff.is(category));
	}

	@Override
	public HookStore.HookHandlerBuilder registerHook() {
		return hookStore.hookBuilder();
	}

	@Override
	public Hooks.HookContext triggerHook(HookType type) {
		return Hooks.triggerHook(type, this.hookStore);
	}

	@Override
	public HookStore getHookStore() {
		return hookStore;
	}

	public static class ReceiveBuffReport {
		public FoeStageBuff buff;
		public StageBuff<?> rejectBy;
		public boolean accept;
		public static ReceiveBuffReport ofAccept(FoeStageBuff buff) {
			ReceiveBuffReport report = new ReceiveBuffReport();
			report.buff = Objects.requireNonNull(buff);
			report.accept = true;
			return report;
		}
		public static ReceiveBuffReport ofReject(FoeStageBuff buff, StageBuff<?> rejectBy) {
			ReceiveBuffReport report = new ReceiveBuffReport();
			report.buff = Objects.requireNonNull(buff);
			report.rejectBy = Objects.requireNonNull(rejectBy);
			report.accept = false;
			return report;
		}
	}

	@Override
	public void leaveFromStage() {
		super.leaveFromStage();

		final Hooks.FoeBuffContext context = triggerHook(HookType.BEFORE_FOE_LEAVE_STAGE).as();
		context.withFoe(this).run();

		final List<FoeStageBuff> remainBuffs = new ArrayList<>(buffs);
		remainBuffs.removeIf(buff -> buff.is(ORIGINAL_BUFF_CATEGORY));
		Collections.reverse(remainBuffs);
		for (FoeStageBuff buff : remainBuffs) {
			removeBuff(buff);
		}
	}

	/* ***********
	 * Delegate  *
	 *********** */

	public Roll createRoll(VStat vStat) {
		return stage.moveChecker.createOne(this, vStat);
	}

	public Roll createRoll(VStat vStat, int checkRateFixed) {
		return stage.moveChecker.createOne(this, vStat, checkRateFixed);
	}

	public Roll createMoreRollWithAttackContext(AttackContext context, FoeStageBuff createdBy) {
		Roll roll = stage.moveChecker.createMoreWithMove(context.currentMove, context.moreRolls.length);
		roll.createdBy = Objects.requireNonNull(createdBy);
		return roll;
	}

	/* ***********
	 *  Others   *
	 *********** */

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder(100);
		appendStringWithoutHp(builder);
		builder.append(' ');
		builder.append("HP:").append(foe.currentHp).append('/').append(bStats[BStat.HP.ordinal()]);
		builder.append(' ').append('+').append(currentShield).append('/').append(foe.maxShield);
		builder.append(' ').append(buffs);
		return builder.toString();
	}

	public void appendStringWithoutHp(StringBuilder builder) {
		foe.appendStringWithoutHp(builder);
	}
}
